/*
 * Copyright (c) 2016. 恒昌互联网运营中心.
 */
package com.credithc.eagleeye.plugins.mybatis;
import com.alibaba.druid.pool.DruidDataSource;
import com.credithc.eagleeye.agent.factory.EagleEyeAgentFactory;
import com.credithc.eagleeye.constants.RpcTypeEnum;
import com.credithc.eagleeye.plugins.util.LogUtil;
import com.credithc.eagleeye.thread.EagleEyeThreadContext;
import com.credithc.eagleeye.thread.TraceInfo;
import com.credithc.eagleeye.util.FastJsonConvert;
import com.credithc.eagleeye.vo.RpcTraceInfoVo;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.util.Date;
import java.util.List;
import java.util.Properties;
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,RowBounds.class, ResultHandler.class})})
public class EagleEyeMybatisInterceptor implements Interceptor {
    public static final Logger logger = LoggerFactory.getLogger(EagleEyeMybatisInterceptor.class);
    private Properties properties;
    private static final FastDateFormat ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ");
    public static final FastDateFormat ISO_DATETIME_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) args[0];
        Object parameter = args[1];
        String sqlId = mappedStatement.getId();
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        //如果使用了pageHelper插件
        if (args.length == 6) {
            boundSql = (BoundSql) args[5];
        }
        Configuration configuration = mappedStatement.getConfiguration();
        Object returnValue = null;
        long startTime = System.currentTimeMillis();
        returnValue = invocation.proceed();
        long endTime = System.currentTimeMillis();
        long runTime = (endTime - startTime);
        String jdbcUrl = "";
        DataSource dataSource = configuration.getEnvironment().getDataSource();
        if (dataSource instanceof DruidDataSource) {
            jdbcUrl = ((DruidDataSource) dataSource).getUrl();
        }
        //发送到鹰眼
        send2EagleEye(jdbcUrl, configuration.getDatabaseId(), sqlId, showSql(configuration, boundSql), returnValue, startTime, runTime);
        return returnValue;
    }
    private void send2EagleEye(String jdbcUrl, String databaseType, String sqlId, String sql, Object returnValue, long startTime, long runTime) {
        TraceInfo traceInfo = EagleEyeThreadContext.getTraceInfo();
		/*
		//每次都放入鹰眼
		if (traceInfo == null) {
			traceInfo = new TraceInfo();
			EagleEyeThreadContext.putTraceInfo(traceInfo);
		} else {
			traceInfo.addSequenceNo();
		}
		*/
        // 当调用链不为空的时候，再放入鹰眼，防止无用的数据，比如定时任务访问数据库的信息
        if (traceInfo != null) {
            traceInfo.addSequenceNo();
            RpcTraceInfoVo rpcTraceInfoVo = new RpcTraceInfoVo();
            try {
                rpcTraceInfoVo.setRequestDateTime(ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS.format(new Date(startTime)));
                rpcTraceInfoVo.setTraceId(traceInfo.getTraceId());
                rpcTraceInfoVo.setRpcId(traceInfo.getRpcId());
                rpcTraceInfoVo.setRpcType(RpcTypeEnum.DB.getRpcName());
                rpcTraceInfoVo.setServiceCategory("Mybatis");
                String[] sqlIdStrArr = StringUtils.split(sqlId, ".");
                if(sqlIdStrArr!=null&&sqlIdStrArr.length>=2) {
                    rpcTraceInfoVo.setServiceName(sqlIdStrArr[sqlIdStrArr.length-2]);
                    rpcTraceInfoVo.setMethodName(sqlIdStrArr[sqlIdStrArr.length-1]);
                }
                rpcTraceInfoVo.setRequestJson(sql);
                rpcTraceInfoVo.setUserInfo(traceInfo.getUserInfo());
                String serverHost = StringUtils.substringBetween(jdbcUrl, "jdbc:", "?");
                if (StringUtils.isBlank(serverHost)) {
                    serverHost = StringUtils.substringAfter(jdbcUrl, "jdbc:");
                }
                rpcTraceInfoVo.setServerHost(serverHost);
                rpcTraceInfoVo.setRunTime(runTime);
                rpcTraceInfoVo.setResult("OK");
                boolean enableMybatisResult = EagleEyeAgentFactory.getInstance().getEagleEyeAgentConfig().isEnableMybatisResult();
                if(enableMybatisResult) {
                    if(returnValue instanceof String){
                        String strRetsult = (String) returnValue;
                        int mybatisMaxSqlLength = EagleEyeAgentFactory.getInstance().getEagleEyeAgentConfig().getMybatisMaxSqlLength();
                        if(strRetsult.length()>mybatisMaxSqlLength){
                            rpcTraceInfoVo.setResponseJson(strRetsult.substring(0,mybatisMaxSqlLength)+"...");
                        }else{
                            rpcTraceInfoVo.setResponseJson(strRetsult);
                        }
                    }else if(returnValue instanceof List){
                        List listRetsult = (List) returnValue;
                        if(listRetsult.size()<=100){
                            rpcTraceInfoVo.setResponseJson(FastJsonConvert.convertObjectToJSON(returnValue));
                        }else{
                            rpcTraceInfoVo.setResponseJson("result size :"+listRetsult.size()+" rows ...");
                        }
                    }else{
                        rpcTraceInfoVo.setResponseJson(FastJsonConvert.convertObjectToJSON(returnValue));
                    }
                }
            } catch (Exception ex) {
                rpcTraceInfoVo.setResult("ERROR");
                ex.printStackTrace();
            } finally {
                try {
                    EagleEyeAgentFactory.getInstance().send2EagleEye(rpcTraceInfoVo);
                    LogUtil.printSplunkLog(rpcTraceInfoVo);
                } catch (Exception ex) {
                    logger.error("mybatis send2EagleEye error:"+ex.getMessage());
                }
            }
        }
    }
    public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId, long time) {
        String sql = showSql(configuration, boundSql);
        StringBuilder str = new StringBuilder(100);
        str.append(sqlId);
        str.append(":");
        str.append(sql);
        str.append(":");
        str.append(time);
        str.append("ms");
        return str.toString();
    }
    private static String getParameterValue(Object obj) {
        String value = null;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            value = "'" + ISO_DATETIME_FORMAT.format(obj) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";
            }
        }
        return value;
    }
    public static String showSql(Configuration configuration, BoundSql boundSql) {
        boolean enableMybatisSql = EagleEyeAgentFactory.getInstance().getEagleEyeAgentConfig().isEnableMybatisSql();
        if (!enableMybatisSql) {
            return "";
        }

        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql=boundSql.getSql();
        sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        //是否截取sql
        int mybatisMaxSqlLength = EagleEyeAgentFactory.getInstance().getEagleEyeAgentConfig().getMybatisMaxSqlLength();
        if(sql.length()> mybatisMaxSqlLength){
            return sql.substring(0,mybatisMaxSqlLength)+"...";
        }
        //是否替换sql参数
        boolean enableMybatisConvertSql = EagleEyeAgentFactory.getInstance().getEagleEyeAgentConfig().isEnableMybatisConvertSql();
        if(!enableMybatisConvertSql){
            return sql;
        }

        try {
            if (parameterMappings.size() > 0 && parameterObject != null) {
                TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
                if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
                } else {
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    for(ParameterMapping parameterMapping : parameterMappings) {
                        String propertyName = parameterMapping.getProperty();
                        if (metaObject.hasGetter(propertyName)) {
                            Object obj = metaObject.getValue(propertyName);
                            sql = sql.replaceFirst("\\?", getParameterValue(obj));
                        } else if (boundSql.hasAdditionalParameter(propertyName)) {
                            Object obj = boundSql.getAdditionalParameter(propertyName);
                            sql = sql.replaceFirst("\\?", getParameterValue(obj));
                        }
                    }
                }
            }
        } catch (Exception ex) {
        }
        return sql;
    }
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    public void setProperties(Properties properties0) {
        this.properties = properties0;
    }
}
